#include <fstream>
#include <boost/filesystem.hpp>

#include "FileStream.h"

using namespace std;
using namespace boost;
using namespace Stray;
using namespace Stray::Core;

typedef FileStream::ByteArray ByteArray;

class Stray::Core::FileStreamImpl
{
public:
	FileStreamImpl();
	FileStreamImpl(const Stray::String& fileName, FileOpenMode mode);
	~FileStreamImpl();

	void open(const String& fileName, FileOpenMode mode);
	void close();

	Stray::Bool eof();

	void read(void* pByteArray, size_t byteCount);
	void read(ByteArray* pByteArray, size_t byteCount);
	Stray::Uint8 readByte();

	void write(const Stray::Uint8* pByteArray, size_t byteCount);
	void write(const ByteArray& byteArray, size_t byteCount);
	void writeByte(Stray::Uint8 byte);

	size_t seek(Stray::Int32 offset, SeekOrigin origin);

	void flush();

	Stray::Bool good();
	Stray::Bool fail();

	fstream fs_;
};

FileStreamImpl::FileStreamImpl()
{
}

void createFile(const String& fileName)
{
	fstream fs(fileName.toChar(), ios::out);
	fs.close();
}

FileStreamImpl::FileStreamImpl(const Stray::String& fileName, FileOpenMode mode)
{
	open(fileName, mode);
}

FileStreamImpl::~FileStreamImpl()
{
	close();
}

void FileStreamImpl::open(const String& fileName, FileOpenMode mode)
{
	ios::openmode m = ios::binary;
	switch(mode)
	{
	case FOM_APPEND:
		m |= ios::app;
		fs_.open(fileName.toChar(), m);
		break;
	case FOM_CREATE:
		createFile(fileName);
		m |= ios::out | ios::in;
		fs_.open(fileName.toChar(), m);
		break;
	case FOM_CREATE_NEW:
		if(!filesystem::exists(fileName.toChar()))
		{
			createFile(fileName);
			m |= ios::out | ios::in;
			fs_.open(fileName.toChar(), m);
		}
		break;
	case FOM_OPEN:
		m |= ios::out | ios::in;
		fs_.open(fileName.toChar(), m);
		break;
	case FOM_OPEN_OR_CREATE:
		createFile(fileName);
		m |= ios::out | ios::in;
		fs_.open(fileName.toChar(), m);
		break;
	case FOM_TRUNCATE:
		m |= ios::out | ios::in | ios::trunc;
		fs_.open(fileName.toChar(), m);
		break;
	default:
		assert(false);
	}
}

void FileStreamImpl::close()
{
	fs_.close();
}

Stray::Bool FileStreamImpl::eof()
{
	if(fs_.eof()) return B_TRUE;
	else return B_FALSE;
}

void FileStreamImpl::read(void* pByteArray, size_t byteCount)
{
	assert(pByteArray);

	fs_.read(static_cast<char*>(pByteArray), byteCount);
}

void FileStreamImpl::read(ByteArray* pByteArray, size_t byteCount)
{
	assert(pByteArray);

	if(pByteArray->size() < byteCount) pByteArray->resize(byteCount);
	Uint8 *buff = &((*pByteArray)[0]);
	fs_.read(reinterpret_cast<char*>(buff), byteCount);
}

Uint8 FileStreamImpl::readByte()
{
	return fs_.get();
}

void FileStreamImpl::write(const Uint8* pByteArray, size_t byteCount)
{
	assert(pByteArray);

	fs_.write(reinterpret_cast<const char*>(pByteArray), byteCount);
}

void FileStreamImpl::write(const ByteArray& byteArray, size_t byteCount)
{
	if(byteArray.size() < byteCount) byteCount = byteArray.size();

	fs_.write(reinterpret_cast<const char*>(&(*(byteArray.begin()))), byteCount);
}

void FileStreamImpl::writeByte(Uint8 byte)
{
	fs_.put(static_cast<char>(byte));
}

size_t FileStreamImpl::seek(Int32 offset, SeekOrigin origin)
{
	ios::seekdir pos = ios::beg;
	switch(origin)
	{
	case SO_BEGIN:
		break;
		pos = ios::beg;
	case SO_CURRENT:
		pos = ios::cur;
		break;
	case SO_END:
		pos = ios::end;
		break;
	default:
		assert(false);
	}

	return static_cast<size_t>(fs_.seekg(offset, pos).tellg());
}

void FileStreamImpl::flush()
{
	fs_.flush();
}

Stray::Bool FileStreamImpl::good()
{
	if(fs_.good()) return B_TRUE;
	else return B_FALSE;
}

Stray::Bool FileStreamImpl::fail()
{
	if(fs_.fail()) return B_TRUE;
	else return B_FALSE;
}


FileStream::FileStream()
	:spImpl_(new FileStreamImpl)
{
}

FileStream::FileStream(const String& fileName, FileOpenMode mode)
	:spImpl_(new FileStreamImpl(fileName, mode))
{
}

FileStream::~FileStream()
{
}

void FileStream::open(const String& fileName, FileOpenMode mode)
{
	assert(spImpl_.get());
	spImpl_->open(fileName, mode);
}

void FileStream::close()
{
	assert(spImpl_.get());
	spImpl_->close();
}

Stray::Bool FileStream::eof()
{
	assert(spImpl_.get());
	return spImpl_->eof();
}

void FileStream::read(void* pByteArray, size_t byteCount)
{
	assert(spImpl_.get());
	spImpl_->read(pByteArray, byteCount);
}

void FileStream::read(ByteArray* pByteArray, size_t byteCount)
{
	assert(spImpl_.get());
	spImpl_->read(pByteArray, byteCount);
}

Uint8 FileStream::readByte()
{
	assert(spImpl_.get());
	return spImpl_->readByte();
}

void FileStream::write(const Stray::Uint8* pByteArray, size_t byteCount)
{
	assert(spImpl_.get());
	spImpl_->write(pByteArray, byteCount);
}

void FileStream::write(const ByteArray& byteArray, size_t byteCount)
{
	assert(spImpl_.get());
	spImpl_->write(byteArray, byteCount);
}

void FileStream::writeByte(Uint8 byte)
{
	assert(spImpl_.get());
	spImpl_->writeByte(byte);
}

size_t FileStream::seek(Int32 offset, SeekOrigin origin)
{
	assert(spImpl_.get());
	return spImpl_->seek(offset, origin);
}

void FileStream::flush()
{
	assert(spImpl_.get());
	spImpl_->flush();
}

Bool FileStream::good()
{
	assert(spImpl_.get());
	if(spImpl_->fs_ == false) return B_FALSE;
	return spImpl_->good();
}

Bool FileStream::fail()
{
	if(good()) return B_FALSE;
	else return B_TRUE;
}